Skip to content

Conversation

@wuetj
Copy link

@wuetj wuetj commented Nov 26, 2025

This adds a missing hflag update after updating the xpsr.
Currently when using an Arm Cortex-M CPU, which does not feature ARM_FEATURE_M_SECURITY, the EXC_RETURN interrupt is not triggered.
After some debugging it turns out the v7m_handler_mode condition here is never true:

if (arm_dc_feature(s, ARM_FEATURE_M_SECURITY) ||
(s->v7m_handler_mode && arm_dc_feature(s, ARM_FEATURE_M))) {
s->base.is_jmp = DISAS_BX_EXCRET;
}

v7m_handler_mode is evaluated based on the HANDLER flag which is set when rebuilding hflags:

unicorn/qemu/target/arm/helper.c

Lines 11592 to 11594 in c24c9eb

if (arm_v7m_is_handler_mode(env)) {
FIELD_DP32(flags, TBFLAG_M32, HANDLER, 1, flags);
}

Since setting IPSR using uc_reg_write does not rebuild hflags, qemu doesn't realize that it's currently in an exception and returning from an exception causes a hard fault.
This fixes this by rebuilding hflags after updating xPSR.
This doesn't matter for processors with the security feature since the condition is always true in that case. I've updated the test to catch this.

The previously used UC_MODE_MCLASS causes UC_CPU_ARM_CORTEX_M33 to be used. This CPU features ARM_FEATURE_M_SECURITY for which handler mode checks are skipped upopn exception return. This reveals a previously undetected issue.
Modifying xpsr might change processor state, after which the cached TBFLAGS need to be rebuild.
@wuetj wuetj force-pushed the fix/arm/xpsr_hflag_update branch from aae4eae to 565aba0 Compare November 26, 2025 20:50
@wuetj wuetj changed the base branch from master to dev November 26, 2025 20:50
uc_common_setup(&uc, UC_ARCH_ARM, UC_MODE_THUMB | UC_MODE_MCLASS, code,
sizeof(code) - 1, UC_CPU_ARM_CORTEX_A15);
uc_common_setup(&uc, UC_ARCH_ARM, UC_MODE_THUMB, code,
sizeof(code) - 1, UC_CPU_ARM_CORTEX_M7);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you create a second test instead changing the used CPU? Maybe add a CPU parameter to the function and write two tests simply calling test_arm_m_exc_return.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why UC_CPU_ARM_CORTEX_A15 is being used here since it's not an M-class CPU. UC_MODE_MCLASS overwrites the cpu model with UC_CPU_ARM_CORTEX_M33, so this value is ignored anyways.
I could create two separate test cases for UC_CPU_ARM_CORTEX_M33 (with M security feature) and UC_CPU_ARM_CORTEX_M4 (no M security feature) if necessary.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This are two different code path so we should have two tests. One for M33 and one for this bug.

Copy link
Author

@wuetj wuetj Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check for EXC_RETURN is simply always true if the CPU has security extension, since in that case the EXC_RETURN code can also be triggered in thread mode.

* To avoid having to do multiple comparisons in inline generated code,
* we make the check we do here loose, so it will match for EXC_RETURN
* in Thread mode. For system emulation do_v7m_exception_exit() checks
* for these spurious cases and returns without doing anything (giving
* the same behaviour as for a branch to a non-magic address).

If EXC_RETURN works for the M7, it will always work for the M33.

I can add tests for both the M7 and M33 though, if you think it's necessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants